using System;
using System.Collections;


namespace Model
{
	[Serializable]
	public struct UserAmountPair
	{
		public string username;
		public decimal amount;

		public override bool Equals(object otherObj)
		{
			UserAmountPair otherPair = (UserAmountPair)otherObj;
			return (username.Equals(otherPair.username) && amount == otherPair.amount);
			
		}

        public string ToIOUString()
        {
            if (amount > 0)
                return username + " owes you " + amount + "$";
            else
                return "You owe " + username + " " + (0-amount) + "$";
        }

		/// <summary>
		/// Compares two lists of user amount pairs
		/// </summary>
		/// <param name="oldList"></param>
		/// <param name="newList"></param>
		/// <returns></returns>
		public static bool PairListEquals(ArrayList oldList, ArrayList newList)
		{// TODO: find a more clever way to compare collections
			if (!oldList.Count.Equals(newList.Count))	// if the size is different
			{
				return false;
			}

			foreach(UserAmountPair iPair in oldList)	// if one is a subset of the other
			{
				if (!newList.Contains(iPair))
				{
					return false;
				}
			}
			// mismatch not found
			return true;
		}
	}

	public struct ReportSharedTransaction
	{
		public string giver;
		public string receiver;
		public decimal amount;

        /// <summary>
        /// Result of the report in string format.
        /// </summary>
        /// <returns>receiver + " must get " + amount + "$ from " + giver + "."</returns>
        public override string ToString()
        {
            return receiver + " must get " + amount + "$ from " + giver + ".";
        }

        /// <summary>
        /// is the given user involved in this shared scenario
        /// </summary>
        /// <param name="user">The user to check for</param>
        /// <returns>true iff the user has given or recieved according to this transaction</returns>
        public bool IsInvolved(string user)
        {
            if (giver.Equals(user) || receiver.Equals(user))
                return true;
            else
                return false;
           
        }
	}

	public struct ExpRevPair
	{
		public decimal expense;
		public decimal revenue;
	}

	/// <summary>
	/// Represent a single transaction entered by the user. Get the information from the database and make it available for other model.
	/// </summary>
	[Serializable]
	public class Transaction
	{
		private uint transactionID;
		private DateTime date; // some text
		public enum TransactionType
		{
			Expense,
			Revenue,
			Transfer
		}
		private string type;
		private string creator; 
		private string lastModifiedBy; // the last user who modified this transaction. 
		private string subject;
		private bool modified = false;
		 		
		/// <summary>
		/// For Expense, involvedConsumers are those who use the purchase
		/// For Revenue, involvedConsumers are those who receive the revenue
		/// For Transfer, involvedPayees = involvedConsumers
		/// </summary>
		private ArrayList involvedConsumers; // could be one user or many (Each object is a UserAmountPair)
	

		/// <summary>
		/// For Expense, involvedPayees are those who use the purchase
		/// For Revenue, no involvedPayees
		/// For Transfer, involvedPayees = involvedConsumers
		/// </summary>
		
		/// <summary>
		/// does the transaction have a notification
		/// </summary>
		public bool IsNotify
		{
			get
			{
				return modified;
			}
			set
			{
				modified = value;
			}
		}

		public string Subject{
			get
			{
				return subject;
			}
			set
			{
				subject = value;
			}
			}

		private ArrayList involvedPayees; 
		private string sourceExternalAccount; // Cash - account name
		private string destExternalAccount;
		private string categoryDescription; //what is this???
		public string Type
		{
			get {return type;}
			set {type=value;}
		}

		public string LastModifiedBy	// TODO: This needs maintanance whenever the transaction is modified, but not viewed
		{
			get {return lastModifiedBy;}
			set {lastModifiedBy=value;}
		}

		public string SourceExternalAccount
		{
			get
			{
				return sourceExternalAccount;
			}
			set
			{
				sourceExternalAccount = value;
			}
		}
		public string DestExternalAccount
		{
			get
			{
				return destExternalAccount;
			}
			set
			{
				destExternalAccount = value;
			}
		}
		public ArrayList InvolvedPayees
		{	
			get
			{
				return involvedPayees;
			}
			set
			{
				involvedPayees = value;
			}
		}
		public string Creator
		{	
			get
			{
				return creator;
			}
			set
			{
				creator = value;
			}
		}
		public uint TransactionID
		{	
			get
			{
				return transactionID;
			}
			set
			{
				transactionID = value;
			}
		}

		public DateTime Date
		{	
			get
			{
				return date;
			}
			set
			{
				date = value;
			}
		}

		public ArrayList InvolvedConsumers
		{	
			get
			{
				return involvedConsumers;
			}
			set
			{
				involvedConsumers = value;
			}
		}

		public string CategoryDescription
		{	
			get
			{
				return categoryDescription;
			}
			set
			{
				categoryDescription = value;    
			}
		}

		
		
		public Transaction()
		{
			transactionID = 0;
			involvedPayees=new ArrayList(0);
			involvedConsumers=new ArrayList(0);
			sourceExternalAccount=destExternalAccount=categoryDescription=creator=type="";
			date = new DateTime(0);

		}
		
		
		public Transaction(string iniDate, string iniType,string iniCreator, ArrayList iniConsumers,
			ArrayList iniPayees, string iniSourceAccount,string iniDestAcct, string iniCategoryDescrip)
		{
			date = DateTime.Parse(iniDate); 
			type = iniType; 
			creator = iniCreator; 
			involvedConsumers = iniConsumers; 
			involvedPayees = iniPayees; 
			sourceExternalAccount = iniSourceAccount ; 
			destExternalAccount = iniDestAcct;
			categoryDescription = iniCategoryDescrip;
			transactionID = 0;
		}

		public Transaction(string iniDate, string iniType,string iniCreator, ArrayList iniConsumers,
			ArrayList iniPayees, string iniSourceAccount,string iniDestAcct,string iniSubject, string iniCategoryDescrip)
		{
		date = DateTime.Parse(iniDate); 
		type = iniType; 
		creator = iniCreator; 
		involvedConsumers = iniConsumers; 
		involvedPayees = iniPayees; 
		sourceExternalAccount = iniSourceAccount ; 
		subject = iniSubject;
		destExternalAccount = iniDestAcct;
		categoryDescription = iniCategoryDescrip;
		transactionID = 0;
		}

		/// <summary>
		/// returns an ArrayList of UserNames that are involved ina transaction ina string 
		/// </summary>
		/// <returns></returns>

		
		/// <summary>
		/// Searches the list of payees and consumers to determine if a user is involved in a transaction or not
		/// </summary>
		/// <param name="username"></param>
		/// <returns>true if a user is involved.</returns>
		public bool isUserInvolved(string username)
		{
			ArrayList temp = new ArrayList();
			temp = this.getInvolvedUsers();
			for (int i = 0; i < temp.Count; i++)
			{
				if (username.Equals(temp[i]))
				{
					return true;
				}
			}
			return false;
		}


		/// <summary>
		/// goes through the list of involved consumers and payees and generates a list of "who owes whom".
		/// </summary>
		/// <returns>ArrayList of ReportSharedTransaction</returns>
		public ArrayList generateSummaryReport()
		{
			ArrayList recordList = new ArrayList();
			ArrayList netList = new ArrayList();

			#region logic for Expense
			
			if (this.Type.Equals("Expense"))
			{
			
				decimal amountPayed = 0;
				decimal amountConsumed = 0;
			
				decimal totalAmount=0;

				// Get total amount
				for (int i = 0; i<this.InvolvedPayees.Count; i++)
				{
					// get the total amount involved
					totalAmount+=((UserAmountPair)this.InvolvedPayees[i]).amount;
					
					// find if user is payee, and it so then get amount payed
					/*if (((UserAmountPair)this.InvolvedPayees[i]).username.Equals(this.Creator))
					{
						amountPayed = ((UserAmountPair)this.InvolvedPayees[i]).amount;
					}*/
				}

			
				// This amountPayed is subtracted from user's bank account
				// TODO
				// Mustansir - 21st March
				Console.WriteLine(amountPayed);
				// TODO

				// Need to search if payee exists in the consumer list
				// Add amountToPay in netList
				for (int i = 0; i < this.InvolvedPayees.Count; i++)
				{
					string payeeName = ((UserAmountPair)this.InvolvedPayees[i]).username;
					decimal amountToGive = -((UserAmountPair)this.InvolvedPayees[i]).amount;
				
					// Search if payee is consumer as well
				
					for (int j = 0; j < this.InvolvedConsumers.Count; j++)
					{
						if ( ((UserAmountPair)this.InvolvedConsumers[j]).username.Equals(payeeName))
						{
							amountToGive += ((UserAmountPair)this.InvolvedConsumers[j]).amount;
						}
					}

					UserAmountPair temp = new UserAmountPair();
					temp.username = payeeName;
					temp.amount = amountToGive;
					netList.Add(temp);
				}

				// Check if consumer exists in the netList
				// If he/she doesnt then he/she wasn't a payee so add to netList
			
				for (int i = 0; i < this.InvolvedConsumers.Count; i++)
				{
					string consumerName = ((UserAmountPair)this.InvolvedConsumers[i]).username;
					bool consumerFound = false;

					for (int j = 0; j < netList.Count; j++)
					{
						if ( ((UserAmountPair)netList[j]).username.Equals(consumerName))
						{
							// consumer exists so ignore
							consumerFound = true;
						}
					}

					// Consumer not found in netList so add to it
					if (consumerFound == false)
					{
						UserAmountPair temp = new UserAmountPair();
						temp.username = consumerName;
						temp.amount = ((UserAmountPair)this.InvolvedConsumers[i]).amount;
						netList.Add(temp);
					}
				}

				// Print out everyone in the netList
				// For Debugging
				for (int i = 0; i < netList.Count; i++)
				{
					string name = ((UserAmountPair)netList[i]).username;
					decimal amount = ((UserAmountPair)netList[i]).amount;

					Console.WriteLine(name + " has to pay "+amount);
				}

				// To generate report for shared transactions
				for (int i = 0; i < netList.Count; i++)
				{
					UserAmountPair user1 = new UserAmountPair();
					UserAmountPair user2 = new UserAmountPair();
				
					for (int j = 0; j < netList.Count; j++)
					{
						// get top user
						user1 = (UserAmountPair)netList[i];
					
						// get current user compared to
						user2 = (UserAmountPair)netList[j];
						
						// if user1 has to pay
						if (user1.amount > 0)
						{
							// if user2 has to receive
							if (user2.amount < 0)
							{
								// user1 owes less than total owed to user 2
								if (Math.Abs(user2.amount) >= Math.Abs(user1.amount))
								{
									ReportSharedTransaction temp = new ReportSharedTransaction();
									temp.giver = user1.username;
									temp.receiver = user2.username;
									temp.amount = Math.Abs(user1.amount);
									// Add to record list
									recordList.Add(temp);
								
									user2.amount+= user1.amount;
									user1.amount = 0;

								}

									// user1 owes more than total owed to user 2
								else
								{
									ReportSharedTransaction temp = new ReportSharedTransaction();
									temp.giver = user1.username;
									temp.receiver = user2.username;
									temp.amount = Math.Abs(user2.amount);
									// Add to record list
									recordList.Add(temp);
								
									user2.amount = 0;
									user1.amount+= user2.amount;
								}

								// Overwrite since changes have been made
								netList[i] = user1;
								netList[j] = user2;
							}
						}
					}
				}// end To generate report for shared transactions
#if (DEBUG)
				// To view all reports

				for (int i = 0; i<recordList.Count; i++)
				{
					ReportSharedTransaction temp;
					temp = (ReportSharedTransaction)recordList[i];

					Console.WriteLine(temp.giver + " gives "+temp.receiver+" "+temp.amount);
				}
#endif
			}// end of Expense
			
			#endregion
			
			
			#region logic for Revenue
			
			if (this.Type.Equals("Revenue"))
			{
				// To calculate total amount
				decimal revenueAmount = 0;
				for (int i = 0; i < this.InvolvedConsumers.Count; i++)
				{
					revenueAmount += ((UserAmountPair)this.InvolvedConsumers[i]).amount;
				}

				// Need to search if payee exists in the consumer list
				// Add amountToPay in netList
				for (int i = 0; i < this.InvolvedPayees.Count; i++)
				{
					string payeeName = ((UserAmountPair)this.InvolvedPayees[i]).username;
					decimal amountToGet = ((UserAmountPair)this.InvolvedPayees[i]).amount;
				
					// Search if payee is consumer as well
				
					for (int j = 0; j < this.InvolvedConsumers.Count; j++)
					{
						if ( ((UserAmountPair)this.InvolvedConsumers[j]).username.Equals(payeeName))
						{
							amountToGet -= ((UserAmountPair)this.InvolvedConsumers[j]).amount;
						}
					}

					UserAmountPair temp = new UserAmountPair();
					temp.username = payeeName;
					temp.amount = amountToGet;
					netList.Add(temp);
				}

				// Check if consumer exists in the netList
				// If he/she doesnt then he/she wasn't a payee so add to netList
			
				for (int i = 0; i < this.InvolvedConsumers.Count; i++)
				{
					string consumerName = ((UserAmountPair)this.InvolvedConsumers[i]).username;
					bool consumerFound = false;

					for (int j = 0; j < netList.Count; j++)
					{
						if ( ((UserAmountPair)netList[j]).username.Equals(consumerName))
						{
							// consumer exists so ignore
							consumerFound = true;
						}
					}

					// Consumer not found in netList so add to it
					if (consumerFound == false)
					{
						UserAmountPair temp = new UserAmountPair();
						temp.username = consumerName;
						// Got money but doesn't deserve the money
						temp.amount = -((UserAmountPair)this.InvolvedConsumers[i]).amount;
						netList.Add(temp);
					}
				}


				// To generate report for shared transactions
				for (int i = 0; i < netList.Count; i++)
				{
					UserAmountPair user1 = new UserAmountPair();
					UserAmountPair user2 = new UserAmountPair();
				
					for (int j = 0; j < netList.Count; j++)
					{
						// get top user
						user1 = (UserAmountPair)netList[i];
					
						// get current user compared to
						user2 = (UserAmountPair)netList[j];

						// if user1 has to receive
						if (user1.amount > 0)
						{
							// if user2 has to give
							if (user2.amount < 0)
							{
								// user1 needs less than total user2 has to give
								if (Math.Abs(user2.amount) >= Math.Abs(user1.amount))
								{
									ReportSharedTransaction temp;
									temp.giver = user2.username;
									temp.receiver = user1.username;
									temp.amount = Math.Abs(user1.amount);
									// Add to record list
									recordList.Add(temp);
								
									user2.amount+= user1.amount;
									user1.amount = 0;

								}

									// user1 needs more than total owed by user2
								else
								{
									ReportSharedTransaction temp = new ReportSharedTransaction();
									temp.giver = user2.username;
									temp.receiver = user1.username;
									temp.amount = Math.Abs(user2.amount);
									// Add to record list
									recordList.Add(temp);
								
									user2.amount = 0;
									user1.amount+= user2.amount;
								}

								// Overwrite since changes have been made
								netList[i] = user1;
								netList[j] = user2;
							}
						}
					}

				}// end To generate report for shared transactions

				for (int i = 0; i<recordList.Count; i++)
				{
					ReportSharedTransaction temp;
					temp = (ReportSharedTransaction)recordList[i];
					Console.WriteLine(temp.giver + " gives "+temp.receiver+" ");
				}
			}
			#endregion

			return recordList;
			
		}
		
		/// <summary>
		/// If the transaction is an expense, find the total amount that a payee paid.
		/// If it is a revenue, find the total amount that a consumer used.
		/// </summary>
		/// <param name="username"></param>
		/// <returns></returns>
		public decimal InvolvedUserAmount(string username)
		{
			if (this.Type.Equals("Expense"))
			{
				for (int i = 0; i < this.InvolvedPayees.Count; i++)
				{
					UserAmountPair temp = (UserAmountPair)this.InvolvedPayees[i];
					if (temp.username.Equals(username))
						return temp.amount;
				}
			}
			else if (this.Type.Equals("Revenue"))
			{
				for (int i = 0; i < this.InvolvedConsumers.Count; i++)
				{
					UserAmountPair temp = (UserAmountPair)this.InvolvedConsumers[i];
					if (temp.username.Equals(username))
						return temp.amount;
				}
			}
			return 0;
		}

		/// <summary>
		/// Finds out if this transaction involve more that one user.
		/// </summary>
		/// <returns></returns>
		public bool isShared()
		{
			ArrayList temp = this.getInvolvedUsers();
			for (int i = 0; i < temp.Count-1; i++)
			{
				for (int j = 1; j < temp.Count; j++)
				{
					if (temp[i].Equals(temp[j]) == false)
						return true;
				}
			}
			return false;
		}

		/// <summary>
		/// This function returns all users involved in the transaction
		/// </summary>
		/// <returns></returns>
		public ArrayList getInvolvedUsers()
		{
			ArrayList users = new ArrayList();
			for (int i = 0; i < involvedPayees.Count; i++)
			{
				UserAmountPair temp = (UserAmountPair)involvedPayees[i];
				users.Add(temp.username);
			}

			for (int i = 0; i < involvedConsumers.Count; i++)
			{
				UserAmountPair temp = (UserAmountPair)involvedConsumers[i];
				users.Add(temp.username);
			}

			for (int i = 0; i < users.Count-1; i++)
			{
				for (int j = 1; j < users.Count; j++)
				{
					if (i != j)
					{
						if (users[i].Equals(users[j]))
						{
							users.RemoveAt(j);
							j--;
						}
					}
				}

			
			}

			return users;
		}

		public decimal getTotalAmount()
		{
			decimal amount = 0;
			for (int i = 0; i < involvedPayees.Count; i++)
			{
				UserAmountPair temp = (UserAmountPair)involvedPayees[i];
				amount += Decimal.Parse(temp.amount.ToString());
			}
			return amount;
		}

		/// <summary>
		/// ID and source account name is ignored
		/// </summary>
		/// <param name="tx1"></param>
		/// <param name="tx"></param>
		/// <returns></returns>
		public  bool EqualsIgnoreAccount(Transaction tx2)
		{
			bool result = type.Equals(tx2.type) ;
				result =result && UserAmountPair.PairListEquals(this.InvolvedPayees,tx2.InvolvedPayees);
				result =result && UserAmountPair.PairListEquals(this.involvedConsumers,tx2.involvedConsumers);

				result =result && subject.Equals(tx2.subject) ;
				result =result && categoryDescription.Equals(tx2.categoryDescription);
			return result;
		}

		/// <summary>
		/// checks if this user was in the list of payees
		/// </summary>
		/// <param name="username"></param>
		/// <returns></returns>
		public bool IsPayee(string username)
		{
			foreach (UserAmountPair iUser in this.InvolvedPayees)
			{
				if (iUser.username.Equals(username))
					return true;
			}

			// not found
		return false;
		}

		/// <summary>
		/// Checks if the user is in the list of consumers
		/// </summary>
		/// <param name="username"></param>
		/// <returns></returns>
		public bool IsConsumer(string username)
		{
			foreach (UserAmountPair iUser in this.involvedConsumers)
			{
				if (iUser.username.Equals(username))
					return true;
			}

			// not found
			return false;
		}

		
		
	}
}
